<?php
/**
 * Таблица сопоставления URL к сигналам
 */
 include_once('agplatform_exceptions.php');
 include_once('./inc/connectManager.php');
 include_once('./inc/lib/AGPsource/connectManager.php');
 include_once('./inc/lib/Game/connectManager.php'); 


 class AGPlatform_Routing
 {
 	static public $instance = null; //текущий экземпляр, для синглтона
 	
 	//private $sigslot = null; //обьект ezSignalSlots
 	
 	private $signals_ns = Array('sys', 'error', 'game', 'agp', 'core', 'service', 'test');   //массив разрешенных пространств имен для сигналов
	
	/**
 	 * хранит внутренюю таблицу роутинга:
 	 * 
 	 * url => array(signal1,... signalN)
 	 * 
 	 */
	private $route = Array();
	
	
	/**
	 * Обязательные сигналы, пред любым URL
	 * 'sys_getbrowserinfo', 'sys_checkbrowserinfo', 'sys_output_start', , 'sys_output_stop' 'sys_acl',  'sys_logs','sys_file_cache', 'sys_fast_cache',
	 */
	private $pre_dispatch_signals = Array('sys_init', 'sys_output_start', 'sys_pqp_start', 'sys_logs');
	
	/**
	 * 'sys_pm_start',  'sys_db' 'sys_templates',
	 * Обязательные сигналы после обработки
	 * !Внимание! Эти сигналы не сработают, если было исключение в процессе обработки
	 */
	private $post_dispatch_signals = Array('sys_pqp_stop', 'sys_output_stop');
	
	/**
	 * Если установлен в True - остановка диспетчиризации сигналов
	 */
	static public $stop_flag = false;
	
	/**
	 * сигналы для случая пустого URL
	 */
	private $default_empty_url_signal = Array();
	
	
	/**
	 * Набор параметров для передачи каждому обработчику сигнала
	 */
	static public $param = Array();
	
	
	
	//===========
	// собственный механизм сигналов
	private $_connecting = Array();  //хранит присоединения к сигналам в виде таблицы: сигнал - массив к нему подключенных
	
	
	public $emit_results = Array(); // хранит результат вызова всех сигналов (то, что возвращает функция)
	
	
	
	
	/**
	 * Конструктор класса, приватный
	 * @return 
	 */
	private function __construct()
	{
		//инициализируем компонент сигнала
		//require_once('lib/ezSigSlots/ezc_signal.php');
				
		//$this->sigslot = new ezcSignalCollection();
				
		//конструирование таблицы роутинга
		$this->_constructRoutesTable();
				
		//инициализируем параметры
		$this->createParam();
				
		$config = parse_ini_file('./inc/config/game_config.ini', true);	
				
		$this->setParamEntity('config', $config);
	
				
		AGPlatform_Routing::$instance = $this;
				
		return true;
	}
	
	/**
	 * Возвращает текущий экземпляр обьекта
	 * @return AGPlatform_Routing $instance
	 */
	public static function getInstance()
	{
		if (isset(AGPlatform_Routing::$instance))
		return AGPlatform_Routing::$instance;
		else
			return new AGPlatform_Routing();
	}
	
	
	/**
	 * Метод конструирует таблицу роутинга URL на сигналы.
	 * 
	 * @return Boolean
	 */
	private function _constructRoutesTable()
	{
		// здесь только служебные URL и тестовые сигналы
		// обычно, реальные URL обрабатываются игровыми классами (подключаемыми)		
		$this->route = Array();
		
		//теперь экспортировать таблицу роутинга  для базовых классов
		$platform_routing = Platform_connectManager::exportRouting();
		$agp_routing = AGPsource_connectManager::exportRouting();
		$game_routing = Game_connectManager::exportRouting();
		
		
		$this->route = array_merge($this->route, $platform_routing, $agp_routing, $game_routing);
		
    	return true;
	}
	
	
	
	//для оптимизации, подключение только используемых сигналов
	private function _autoconnect_signal($signal = null)
	{
		if (empty($signal)) return false;   //throw new AGPlatform_StopRouting_Signal_Exception();
		else
			{
				try
				{
					$_signal_ns = explode('_', $signal);
				
					if (($_signal_ns === FALSE) || ((is_array($_signal_ns)) && (count($_signal_ns) == 1) && ($_signal_ns[0] === $signal)))
					{
						//wrong signal namespace or uncnown signal
						throw new AGPlatform_SignalSlot_Exception('Signal must be in specified namespace');
					}
					else
					{
						$_signal_ns = $_signal_ns[0] . '_';
					}
					
					if (in_array($_signal_ns, AGPsource_connectManager::$prefix))
					{
						//значит попытка искать в других классах
						//var_dump(AGPsource_connectManager::signalManager($signal, $this));
						if (AGPsource_connectManager::signalManager($signal, $this) === true)
						{
							//значит там подключили сигнал все же
							return true;
						}	
					}
					
					if (in_array($_signal_ns, Game_connectManager::$prefix) !== FALSE)
					{
						//значит попытка искать в других классах
						if (Game_connectManager::signalManager($signal, $this) === true)
						{
							//значит там подключили сигнал все же
							return true;
						}	
					}
					
					if (in_array($_signal_ns, Platform_connectManager::$prefix) !== FALSE)
					{
						//значит попытка искать в других классах
						if (Platform_connectManager::signalManager($signal, $this) === true)
						{
							//значит там подключили сигнал все же
							return true;
						}	
					}
				
				}
				catch (AGPlatform_Unknown_Signal_Exception $e)
				{
					//что делать с неизвестными сигналами?
					//$log = Zend_Registry::getInstance()->get('error_log');
					//$log->log('Use unknown sygnal in autoconenct function', Zend_Log::WARN);
				}
				
			}
	}
	
	
	/**
	 * Принимает массив сигналов и присоединяет их обработчики
	 * @return 
	 * @param object $signals[optional]
	 */
	private function _autoconnector($signals = null)
	{
		if ((is_array($signals)) || (!empty($signals)))
		{
			foreach ($signals as $signal)
			{
				if (!empty($signal))
				{
					$this->_autoconnect_signal($signal);
				}				
			}
		}
	}
	
	
	
	
	
	/**
	 * инициализация массива параметров
	 * @return 
	 */
	private function createParam()
	{
		AGPlatform_Routing::$param = Array();
	}
	
	/**
	 * Возвращает текущий набор параметров
	 * @return 
	 */
	public function getCurrentParam()
	{
		return AGPlatform_Routing::$param;
	}
	
	/**
	 * устанавливает параметр
	 * @return bool
	 * @param object $param_name[optional]
	 * @param object $param_value[optional] - либо значение, либо массив имя-значение
	 * @param boolean $replace - если true, переменная перезаписывается, если false, добавляется в массив
	 */
	public function setParamEntity($param_name = null, $param_value = null, $replace = true)
	{
		if (!empty($param_name))
		{
			if ($replace === TRUE)
			{
				AGPlatform_Routing::$param[$param_name] = $param_value;
			}
			else
				{
					/** пока не доработано
					 * !TODO: 
					 
					if ((isset(AGPlatform_Routing::$param[$param_name])) && (is_array(AGPlatform_Routing::$param[$param_name])))
					{
						if (is_array($param_value))
						
						
						AGPlatform_Routing::$param[$param_name][$param_value[]]
					}
					*/
				}
			
			
			
			
			return true;
		}
		else
			return false;
	}
	
	
	
	
	/**
	 * подготовка URL к диспетчиризации
	 * @return 
	 * @param object $url[optional]
	 */
	public function prepareURL($url = null)
	{
		if (empty($url)) return false;
		else
			{
				$url = strtolower(substr($url, 0, 1024));
				
				if (strripos($url, '/') === (strlen($url)-1))
				{
					$url = substr($url, 0, strlen($url)-1);
				}
				
				if (!empty($url)) return $url;
				else
					return false;				
			}
	}
	
	
	public function getRoutingToURL($url = null)
	{
		if (empty($url)) return $this->default_empty_url_signal;
		else
			{
				/**
				перебираем все URL, сравнивая с образцом.
				выводим первый, который имеет минимальную длину и совпадает с нашим
                **/
				$result = Array('strlen' => 999, 'signals' => Array()); //найденные
				$url_len = strlen($url);
				
				foreach($this->route as $url_key => $signals)
				{
					if (strpos($url, $url_key) !== FALSE)
					{
						// Если разница длин абсолютная меньше чем предыдущая, перезаписать						
						if (abs((strlen($url_key) - $url_len)) <= $result['strlen'])
						{
							$result['strlen'] = abs((strlen($url_key) - $url_len));
							$result['signals'] = $this->route[$url_key];
							
							//остановить перебор при первом совпадении
							if ($result['strlen'] == 0) break;
						}
					}
				}
				
				if (($result['strlen'] != 999) && (count($result['signals']) > 0) && (!empty($result['signals'])) ) 
				{
					return $result['signals']; //вернем массив сигналов
				}
				else
					return $this->default_empty_url_signal; //иначе дефолтные				
				
			}
	}
	
	
	/**
	 * Собирает полную таблицу сигналов 
	 * @return Array
	 * @param object $signals[optional]
	 */
	public function buildFullSignalsTable($signals = null)
	{
		$full_table = Array();
		
		
		$full_table = array_merge(
									$this->pre_dispatch_signals,
									$signals,
									$this->post_dispatch_signals		
								);
		
		if (empty($full_table))
		{
			throw new AGPlatform_StopRouting_Exception();
		}
		else
			return $full_table;		
	}
	
	/**
	 * Собирает и готовит все параметры из запроса
	 * //
	 * @return 
	 */
	private function prepareHTTPRequest()
	{
		// из запроса убираются все параметры, которые начинаются с символа _, например, _dc
		$tmp = $_REQUEST;
		$httprequest = Array();
		
		foreach ($tmp as $name=>$item)
		{
			$name = substr(strtolower(trim($name)), 0, 1024); //максимальная длина имени переменной 1024 символа
			$item = substr(strtolower(trim($item)), 0, 4096); //максимальная длина значения, передаваемого - 4 Кб
			
			if (strpos($name, '_') === 0)    continue;
			else
			{
				$httprequest[$name] = $item;
			}
		}
		
		if (!empty($httprequest))
		{
			//добавить в параметр
			$this->setParamEntity('httprequest', $httprequest);
		}
		else
			$this->setParamEntity('httprequest', null);
		
		
		
	}
	
	/**
	 * Собственно, проводит всю диспетчиризацию URL-а
	 * @return 
	 * @param object $url[optional]
	 */
	public function dispatchURL($url = null)
	{
		$url = $this->prepareURL($url);
		
		if (!empty($url))
		{
			$this->setParamEntity('url', $url);
		}
		
	    //надо собрать в параметры все необходимые переменные
		$this->prepareHTTPRequest();
		
		$signals_array = $this->buildFullSignalsTable( $this->getRoutingToURL($url) );

		$this->setParamEntity('signals', $signals_array);
		
		//используем автоконнектор
		$this->_autoconnector($signals_array);

		//== а теперь обработаем все
		foreach ($signals_array as $cur_signal)
		{

			//проверить флаг
			if (AGPlatform_Routing::$stop_flag === FALSE)
			{
				$this->setParamEntity('now_signal', $cur_signal);
				
				try
				{
					$this->emit($cur_signal, AGPlatform_Routing::getCurrentParam());
				}
				catch (AGPlatform_StopRouting_Signal_Exception $e)
				{
					
					AGPlatform_Routing::$stop_flag = true;
				}
			}
			else
				break;
		}
		
		//если все ок, вернуть true
		// если остановлено было - сгенерировать исключение
		if (AGPlatform_Routing::$stop_flag == true)
		{
			$this->emit('sys_exception', 'Routing are stopping');
		}
		else
			return true;		
	}
	
	
	
	/**
	 * Для всех внешних модулей вызивать только этот емит, а не напрямую
	 * @return 
	 * @param object $signal[optional]
	 * @param array $addition - дополнительные параметры именно этому сигналу
	 */
	public function emit($signal = null, $param = null, $addition = null)
	{
		if (!empty($signal))
		{
			if (empty($param)) $param = AGPlatform_Routing::getCurrentParam();
			
			$_signals = Array();
			
			if (is_string($signal))
			{
				$_signals[] = $signal;
			}
			else
			if (is_array($signal))
			{
				$_signals = array_merge($_signals, $signal);
			}
			
			
			foreach ($_signals as $sig)
			{				
				//надо бы посмотреть, может уже присоединен?
				if ($this->isConnected($sig) === FALSE)
				{
						$this->_autoconnect_signal($sig);
					
						$this->_emit($sig, $param, $addition);
				}
				else
					{
						$this->_emit($sig, $param, $addition);
					}
			}
			
			return true;
		}
	}
	
	/**
	 * Враппер для коннекта с класса автозагрузки
	 * @return 
	 * @param object $signal[optional]
	 * @param object $connector[optional]
	 */
	public function connect($signal = null, $connector = null, $priority = 1000)
	{
		if ( (empty($signal)) || (empty($connector)) || ( (!is_string($connector)) && (!is_array($connector)) ) )
			return false;
		else
			{
				//проверим, попадает ли сигнал в пространство имен
				$_signal_ns = explode('_', $signal);
				
				if (($_signal_ns === FALSE) || ((is_array($_signal_ns)) && (count($_signal_ns) == 1) && ($_signal_ns[0] === $signal)))
				{
					//wrong signal namespace or uncnown signal
					throw new AGPlatform_SignalSlot_Exception('Signal must be in specified namespace');
				}
				else
				{
					$_signal_ns = $_signal_ns[0];
					
					if (in_array($_signal_ns, $this->signals_ns) === FALSE )
					{
						throw new AGPlatform_SignalSlot_Exception('Signal must be in specified namespace');
					}
					else
						{
							//используем встроенный механизм
							$this->_connecting[$signal][$priority][] = $connector;
						}
				}
								
				
				return true;
			}
	}
	
	
	
	/**
	 * внутреняя реализация механизма emmit signals
	 * @return 
	 * @param object $signal[optional]
	 * @param object $param[optional]
	 * @param object $option[optional]
	 */
	private function _emit($signal = null, $param = null, $option = null)
	{
		if (empty($signal)) throw new AGPlatform_SignalSlot_Exception('Empty signal to emitting, error!');
		else
			{
//echo '<pre>';				
//var_dump($this->_connecting);


				
				// сначала сортировка по приоритету, потом по порядку добавления
				$tmp = $this->_connecting[$signal];
				
				if ((empty($tmp)) || (!is_array($tmp))) return false;
				
				ksort($tmp, SORT_NUMERIC);  //сортировка по возрастанию
				
				foreach ($tmp as $prior => $connect)
				{
					if ((!empty($connect)) && (is_array($connect)))
					{
						foreach ($connect as $real_con)
						{
							if (is_callable($real_con) === TRUE)
							{
								//вызиваем!
								$this->emit_results[$signal][] = call_user_func_array($real_con, array($param, $option));
							}
							else
								{
									//невозможно вызвать
									throw new AGPlatform_Unknown_Connect_Exception('Bad or unknown connecting resource: ' . print_r($real_con, true));
								}
						}
					}
				}
			}

	}
	
	/**
	 * Проверяет, есть ли хоть один обработчик для указанного сигнала
	 * @return bool true если есть, false если нет ни одного
	 * @param object $signal[optional]
	 */
	public function isConnected($signal = null)
	{
		if (empty($signal)) return false;
		else
			{
				if ((isset($this->_connecting[$signal])) && 
				    (!empty($this->_connecting[$signal])) && 
				    (count($this->_connecting[$signal]) > 0))
				{
					return true;
				}
				else
					return false;
			}
	}
	
	
	/**
	 * Возвращает массив слушателей указанного сигнала
	 * @return bool false Есл ничего нет или Array
	 * @param object $param[optional]
	 * @param object $option[optional]
	 */
	public function getConnectingTable($signal = null)
	{
		if (empty($signal)) return false;
		else
			{
				if ((isset($this->_connecting[$signal])) && 
				    (!empty($this->_connecting[$signal])) && 
				    (count($this->_connecting[$signal]) > 0))
				{
					return $this->_connecting[$signal];
				}
				else
					return false;
			}
	}
	
	
	
	
	/**
	 * Тестовый сигнал
	 * @return bool
	 * @param object $param[optional]
	 * @signal test_signal_OK
	 */
	static public function testSignalOK($param = null, $option = null)
	{
		
		//var_dump($param);
		
		if (Zend_Registry::isRegistered('ajax_log'))
		{
			$log = Zend_Registry::getInstance()->get('ajax_log');
		
			$log->log($param, Zend_Log::INFO);			
		}
		
		//тест перевода и локализаций
		$options = Array(
			'disableNotices' => true,
			'log' => Zend_Registry::getInstance()->get('ajax_log'),
			'logUntranslated' => true		
		);
		
		AGPlatform_Routing::getInstance()->emit('sys_file_cache');
		$cache = Zend_Registry::getInstance()->get('file_cache');
		
		//
		//$cache = Zend_Cache::factory('Core', 'File');
		Zend_Translate::setCache($cache);		
		
		$translate = new Zend_Translate('tmx', './resources/languages/locales_en_ru.tmx', 'en', $options);		
		
		//$actual = $translate->getList();
		
		//var_dump($actual);
		
		echo '<center><h1>' .$translate->_('testOK'). '</h1></center>';
		
		$translate->setLocale('ru');
		
		echo '<center><h1>' .$translate->_('testOK1'). '</h1></center>';
		
		// test exception logging
		//throw new AGPlatform_Exception('Failure, immediate stop signals routing');		
	}
	// тест исключения для остановки роутинга
	static public function testSignalFailure($param = null, $option = null)
	{
		throw new AGPlatform_StopRouting_Signal_Exception('Failure, immediate stop signals routing');
	}
	
	
 }
